옵저버 패턴
1. 개요
1. 개요
옵저버 패턴은 객체 지향 프로그래밍에서 널리 사용되는 행동 설계 패턴이다. 이 패턴은 한 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들에게 자동으로 알림을 보내고 상태를 갱신할 수 있도록 하는 일대다 의존성을 정의한다.
패턴의 핵심은 주체(Subject)와 관찰자(Observer)라는 두 주요 구성 요소로 이루어진다. 주체는 상태를 가지고 있는 객체이며, 관찰자는 주체의 상태 변화에 관심을 가지는 객체들이다. 주체는 자신에게 등록된 관찰자들의 목록을 유지하며, 상태가 변경되면 목록에 있는 모든 관찰자에게 변경 사실을 알린다. 이로써 객체들은 느슨하게 결합된 상태를 유지하면서도 효율적으로 정보를 전달할 수 있다.
옵저버 패턴은 실시간 데이터 업데이트가 필요한 다양한 소프트웨어 시스템에서 응용된다. 대표적인 예로 GUI 프로그래밍의 이벤트 처리 시스템, 모델-뷰-컨트롤러(MVC) 아키텍처에서 모델과 뷰 간의 동기화, 그리고 뉴스 피드나 주식 시세와 같은 데이터의 발행-구독 모델을 들 수 있다.
이 패턴을 사용하면 주체 객체는 관찰자 객체들의 구체적인 클래스를 알 필요 없이 인터페이스를 통해 상호작용할 수 있다. 결과적으로 시스템의 유연성이 향상되고, 새로운 유형의 관찰자를 추가하거나 제거하는 것이 비교적 용이해진다.
2. 핵심 개념
2. 핵심 개념
옵저버 패턴은 객체 간의 일대다 종속성을 정의하여, 한 객체의 상태가 변할 때 그 객체에 의존하는 모든 객체들이 자동으로 알림을 받고 갱신될 수 있도록 하는 행위 패턴이다. 이 패턴의 핵심은 상태 변화를 주체와 관찰자로 분리하여 느슨한 결합을 달성하는 데 있다.
주요 구성 요소는 Subject와 Observer이다. Subject는 상태를 가지고 있는 중심 객체로, 이 상태의 변화를 관찰해야 하는 Observer 객체들의 목록을 유지 관리한다. Observer는 Subject의 상태 변화에 관심을 가지는 객체로, Subject에 자신을 등록하고 상태 변경 알림을 받기 위한 업데이트 인터페이스를 제공한다.
이 패턴의 동작은 등록, 알림, 해지라는 세 가지 기본 메커니즘으로 이루어진다. Observer는 Subject에 자신을 등록하여 구독 관계를 형성한다. 이후 Subject의 상태가 변경되면, Subject는 자신에게 등록된 모든 Observer 객체들에게 변경 사실을 알린다. Observer는 더 이상 알림을 받을 필요가 없을 때 Subject로부터 자신의 등록을 해지할 수 있다. 이 메커니즘은 Subject가 Observer의 구체적인 클래스에 대해 알 필요 없이, 단지 Observer 인터페이스에 정의된 메서드를 호출하기만 하면 되도록 하여, 두 객체 간의 결합도를 최소화한다.
2.1. Subject (주체)
2.1. Subject (주체)
Subject는 상태를 가지고 있으며, 그 상태의 변화를 Observer들에게 통지하는 책임을 지닌 객체이다. 주체는 관찰자들을 등록하고 제거할 수 있는 메커니즘을 제공하며, 내부 상태가 변경될 때 등록된 모든 관찰자들에게 자동으로 알림을 보낸다. 이는 옵저버 패턴에서 정보의 근원이자 관리자의 역할을 한다.
주체는 일반적으로 다음과 같은 핵심 메서드를 포함하는 인터페이스로 정의된다.
attach(observer): 새로운 관찰자를 목록에 등록한다.detach(observer): 기존 관찰자를 목록에서 제거한다.notify(): 등록된 모든 관찰자들에게 상태 변경을 알리기 위해 각 관찰자의 업데이트 메서드를 호출한다.
주체의 구체적인 구현체는 자신의 상태(예: 온도, 주가, 버튼 클릭 상태)를 관리한다. 상태가 변경되면 notify() 메서드를 호출하여 변경 사실을 알린다. 주체는 관찰자들이 누구인지, 어떤 구체적인 클래스인지에 대해 알 필요가 없다. 단지 Observer 인터페이스를 구현한 객체라는 사실만 알고, 그들의 update() 메서드를 호출하면 된다. 이로 인해 주체와 관찰자 사이의 결합도는 낮게 유지된다.
2.2. Observer (관찰자)
2.2. Observer (관찰자)
옵저버 패턴에서 관찰자는 주체의 상태 변화를 관찰하고 이에 반응하는 객체를 가리킨다. 관찰자는 일반적으로 인터페이스나 추상 클래스로 정의되며, 주체로부터 상태 변경 알림을 받기 위한 update 메서드(또는 유사한 이름의 메서드)를 구현해야 한다. 이 메서드는 주체가 상태를 변경했을 때 호출되는 콜백 함수의 역할을 한다.
관찰자는 구체적인 구현 클래스로 존재하며, 주체에 자신을 등록하여 관찰 대상이 된다. 등록된 관찰자는 주체의 내부 상태에 직접 접근하지 않고, 주체가 제공하는 알림을 통해서만 상태 변화를 인지한다. 이는 두 객체 간의 결합도를 낮추는 핵심 메커니즘이다. 관찰자는 필요에 따라 여러 주체에 동시에 등록될 수 있으며, 주체의 알림을 받은 후에는 자신의 로직을 수행하거나 사용자 인터페이스를 갱신하는 등의 작업을 한다.
관찰자의 주요 책임은 다음과 같이 정리할 수 있다.
책임 | 설명 |
|---|---|
등록/해지 | 주체의 관찰자 목록에 자신을 추가하거나 제거한다. |
알림 수신 | 주체로부터 호출되는 |
응답 처리 | 알림을 받은 후, 필요한 비즈니스 로직이나 UI 업데이트를 수행한다. |
이 패턴에서 관찰자는 수동적인 존재가 아니라, 자신이 관심 있는 상태 변화에 대해 능동적으로 반응하는 독립적인 구성 요소이다. 이벤트 드리븐 프로그래밍, 모델-뷰-컨트롤러 아키텍처, 분산 시스템의 이벤트 처리 등 다양한 맥락에서 이 개념이 적용된다.
2.3. 등록 및 알림 메커니즘
2.3. 등록 및 알림 메커니즘
옵저버 패턴의 �심은 주체(Subject)와 관찰자(Observer) 간의 느슨한 결합을 유지하면서 상태 변화를 전파하는 메커니즘이다. 이 메커니즘은 크게 등록(구독)과 알림의 두 가지 과정으로 구성된다.
등록 과정에서 관찰자는 주체에 자신을 등록하여 향후 상태 변경 알림을 받을 수 있도록 한다. 일반적으로 주체는 등록된 관찰자들의 목록(예: 리스트, 배열, 맵)을 내부적으로 유지 관리한다. 관찰자는 addObserver(), subscribe(), attach()와 같은 메서드를 호출하여 이 목록에 추가된다. 이때 주체는 구체적인 관찰자의 타입이 아닌, Observer 인터페이스에만 의존하므로 새로운 유형의 관찰자를 쉽게 추가할 수 있다.
알림 과정은 주체의 상태가 변경될 때 시작된다. 주체는 setState(), notifyObservers(), publish()와 같은 메서드를 통해 자신에게 등록된 모든 관찰자에게 변경 사실을 통보한다. 이는 등록 목록을 순회하며 각 관찰자의 업데이트 메서드(예: update(), onNotify())를 호출하는 방식으로 이루어진다. 관찰자는 이 호출을 받아 주체로부터 최신 상태 데이터를 가져오거나, 메서드 인자로 전달된 데이터를 사용하여 자신의 상태를 적절히 갱신한다.
과정 | 주체의 역할 | 관찰자의 역할 | 주요 메서드 예시 |
|---|---|---|---|
등록(Subscribe) | 관찰자 목록에 추가 | 주체에 등록 요청 |
|
알림(Notify) | 모든 등록된 관찰자에게 변경 통보 | 업데이트 메서드 실행 |
|
이 메커니즘은 일대다(one-to-many) 의존성을 정의하며, 주체와 관찰자 사이의 직접적인 연결을 최소화한다. 결과적으로 주체는 관찰자들이 어떻게 반응하는지 알 필요 없이 상태 변경만 알리면 되고, 관찰자들은 필요할 때만 주체의 상태를 조회하거나 반응할 수 있다.
3. 구조와 구성 요소
3. 구조와 구성 요소
옵저버 패턴의 구조는 주로 Subject와 Observer라는 두 가지 핵심 인터페이스(또는 추상 클래스)와 이를 구현하는 구체적인 클래스들로 구성된다. 이 패턴은 객체 간의 일대다(one-to-many) 종속성을 정의하여, 한 객체의 상태가 변할 때 그 객체에 의존하는 모든 객체들이 자동으로 통지받고 갱신될 수 있도록 한다.
클래스 다이어그램
일반적인 UML 클래스 다이어그램은 다음과 같은 요소를 보여준다.
구성 요소 | 역할 |
|---|---|
Subject | 관찰 대상이 되는 객체의 인터페이스. 옵저버들을 등록, 제거, 알림하는 메서드를 선언한다. |
ConcreteSubject |
|
Observer | 옵저버 객체들의 인터페이스. |
ConcreteObserver |
|
ConcreteSubject는 Observer 객체들의 컬렉션(예: 리스트)을 관리한다. 상태 변경 시, 컬렉션에 있는 각 Observer의 업데이트 메서드를 순회하며 호출한다.
인터페이스 정의
구조의 기초는 명확하게 정의된 인터페이스에 있다. 일반적인 인터페이스 정의는 다음과 같다.
Subject 인터페이스:
attach(observer: Observer): 새로운 Observer 객체를 목록에 등록한다.detach(observer: Observer): 목록에서 특정 Observer 객체를 제거한다.notify(): 등록된 모든 Observer 객체에게 상태 변경을 알리기 위해 각 객체의update메서드를 호출한다.
Observer 인터페이스:
update():Subject의 상태가 변경되었을 때 호출되는 메서드이다.ConcreteObserver는 이 메서드 내에서Subject로부터 새로운 상태를 가져와 자신의 상태를 갱신한다. 때로는update메서드가 변경된 데이터를 매개변수로 직접 전달받기도 한다[1].
이러한 구조는 Subject와 Observer 사이의 결합도를 최소화하며, 새로운 ConcreteObserver 클래스를 쉽게 추가할 수 있도록 한다.
3.1. 클래스 다이어그램
3.1. 클래스 다이어그램
옵저버 패턴의 구조는 일반적으로 Subject와 Observer라는 두 가지 핵심 인터페이스 또는 추상 클래스를 중심으로 정의된다. 이 패턴의 정적인 관계는 UML 클래스 다이어그램을 통해 명확하게 표현할 수 있다.
주요 구성 요소와 그 관계는 다음과 같다.
구성 요소 | 역할 | 관계 |
|---|---|---|
Subject (또는 Observable) | 관찰 대상이 되는 객체의 인터페이스. Observer 객체들을 등록, 제거, 알림하는 메서드를 선언한다. | ConcreteSubject의 일반화(상속). Observer 객체들에 대한 참조를 유지한다. |
ConcreteSubject | Subject 인터페이스를 구현한 구체적인 주체 객체. 상태가 변경되면 등록된 모든 Observer에게 알린다. | Subject를 구현(Realization) 또는 상속(Inheritance). |
Observer | Subject의 변경을 통보받는 객체의 인터페이스. 업데이트 메서드를 선언한다. | ConcreteObserver의 일반화(상속). |
ConcreteObserver | Observer 인터페이스를 구현한 구체적인 관찰자 객체. Subject의 상태 변경 알림을 받아 자신의 상태를 갱신한다. | Observer를 구현(Realization) 또는 상속(Inheritance). Subject에 대한 참조를 가질 수 있다. |
다이어그램에서 Subject는 Observer에 대한 의존 관계(Dependency)를 가진다. 이는 Subject가 Observer 인터페이스만 알고 있으며, 구체적인 ConcreteObserver 클래스에 직접 의존하지 않음을 의미한다. ConcreteObserver는 자신이 관찰할 ConcreteSubject 객체에 대한 연관 관계(Association)를 맺어, 필요시 해당 Subject의 상태를 질의할 수 있다. 이 구조는 느슨한 결합을 실현하여, Subject와 Observer가 서로 독립적으로 변경되고 재사용될 수 있게 한다.
3.2. 인터페이스 정의
3.2. 인터페이스 정의
옵저버 패턴의 구조를 정의하는 핵심은 Subject와 Observer 인터페이스(또는 추상 클래스)이다. 이 두 인터페이스는 구체적인 구현과 무관하게 패턴의 기본 계약을 규정한다.
Subject 인터페이스는 관찰 대상 객체가 제공해야 하는 메서드를 선언한다. 일반적으로 다음과 같은 세 가지 기본 연산을 포함한다.
attach(observer): Observer 객체를 자신의 목록에 등록(추가)하는 메서드이다.detach(observer): 등록된 Observer 객체를 목록에서 제거하는 메서드이다.notify(): 자신의 상태가 변경되었을 때 등록된 모든 Observer 객체에게 알림을 보내기 위해 호출하는 메서드이다. 이 메서드는 각 옵저버의update()메서드를 순차적으로 호출한다.
Observer 인터페이스는 모든 구체적인 관찰자가 구현해야 하는 업데이트 메서드를 선언한다. 이 인터페이스는 보통 단일 메서드로 구성된다.
update(): Subject로부터 상태 변경 알림을 받았을 때 호출되는 메서드이다. 이 메서드의 시그니처는 구현 방식에 따라 다르다. 간단한 방식은 매개변수가 없는 형태로, 옵저버가 필요하면 주체에게 직접 상태를 요청하는 풀(Pull) 모델을 사용한다. 반면, 변경된 상태 정보를 매개변수로 전달받는 푸시(Push) 모델도 흔히 사용된다.
인터페이스 | 핵심 메서드 | 역할 |
|---|---|---|
| 옵저버를 관리하고 상태 변경 시 알림을 발송하는 주체의 계약을 정의한다. | |
| 주체의 변경 알림을 받아 처리하기 위한 관찰자의 계약을 정의한다. |
이러한 인터페이스 정의는 구체적인 ConcreteSubject와 ConcreteObserver 클래스가 이를 구현함으로써 느슨한 결합을 달성할 수 있게 한다. 주체는 옵저버의 구체적인 클래스를 알 필요 없이 오직 Observer 인터페이스 타입을 통해 통신한다.
4. 작동 원리
4. 작동 원리
옵저버 패턴의 작동 원리는 크게 등록(Subscribe), 상태 변경과 알림(Notify), 해지(Unsubscribe)의 세 가지 과정으로 나뉜다. 이 과정들은 주체(Subject)와 관찰자(Observer) 사이의 느슨한 결합을 유지하며 동적인 관계를 관리한다.
등록 과정에서는 관찰자가 주체에 자신을 등록한다. 일반적으로 주체 객체는 관찰자 객체들의 목록(예: 리스트, 배열, 맵)을 유지한다. 관찰자는 addObserver(), subscribe(), attach()와 같은 메서드를 호출하여 이 목록에 자신을 추가한다. 이때 주체는 관찰자의 인터페이스에만 의존하므로, 구체적인 관찰자 클래스의 내부 구현을 알 필요가 없다.
상태 변경과 알림 과정은 패턴의 핵심이다. 주체의 상태(데이터)가 변경되면, 주체는 자신에게 등록된 모든 관찰자 목록을 순회하며 각 관찰자의 update(), notify()와 같은 미리 약속된 메서드를 호출한다. 이 호출을 통해 상태 변경 사실을 알린다. 관찰자는 이 메서드가 호출되면 주체로부터 새로운 상태 데이터를 가져와(풀 모델) 또는 주체가 매개변수로 전달한 데이터를 사용해(푸시 모델) 자신의 작업을 수행한다. 이 과정은 주체가 직접 각 관찰자의 메서드를 호출하는 방식이지만, 주체는 관찰자의 구체적인 타입을 몰라도 된다는 점에서 다형성에 기반한다.
해지 과정은 등록의 반대이다. 관찰자가 더 이상 주체의 변경을 알 필요가 없을 때, removeObserver(), unsubscribe(), detach()와 같은 메서드를 호출하여 주체의 관찰자 목록에서 자신을 제거한다. 이로 인해 주체는 해당 관찰자에게 더 이상 알림을 보내지 않게 되며, 관찰자 객체는 적절하게 메모리에서 해제될 수 있다. 이 세 과정은 런타임 중에 자유롭게 반복될 수 있어 객체 간의 동적인 관계 구축에 적합하다.
과정 | 주체의 역할 | 관찰자의 역할 | 주요 메서드 예시 |
|---|---|---|---|
등록 | 관찰자 목록에 추가 | 등록 요청을 보냄 |
|
알림 | 상태 변경 시 목록 순회, |
|
|
해지 | 관찰자 목록에서 제거 | 등록 해제 요청을 보냄 |
|
4.1. 등록(Subscribe) 과정
4.1. 등록(Subscribe) 과정
옵저버 패턴에서 Subject에 Observer를 등록하는 과정은 관찰 관계를 설정하는 첫 번째 단계이다. 이 과정은 주체의 상태 변화를 관찰자가 수신할 수 있도록 연결 고리를 만드는 작업이다.
일반적으로 Subject 인터페이스나 클래스는 addObserver(), registerObserver(), subscribe()와 같은 이름의 메서드를 제공한다. 관찰자는 이 메서드를 호출하며, 자신의 참조(예: 객체 인스턴스)를 인자로 전달한다. 주체는 전달받은 관찰자 참조를 내부 컬렉션(예: 리스트, 배열, 맵)에 저장한다. 등록 과정 후의 관계는 다음 표와 같이 정리할 수 있다.
주체(Subject)의 상태 | 관찰자(Observer) 컬렉션 | 설명 |
|---|---|---|
변경 전 | [Observer A, Observer B] | 두 관찰자가 등록된 상태 |
| [Observer A, Observer B, Observer C] | 새로운 관찰자 C가 컬렉션에 추가됨 |
변경 후 | [Observer A, Observer B, Observer C] | 세 관찰자 모두 알림을 받을 수 있음 |
구현 시 고려할 점은 동일한 관찰자의 중복 등록을 방지하는 로직을 추가하는 것이다. 또한, 등록 순서에 따라 알림을 받는 순서가 결정될 수 있으므로, 이에 대한 요구사항을 명확히 하는 것이 중요하다. 등록 과정은 패턴의 동작을 시작하는 필수적인 준비 단계로, 이후 발생하는 상태 변경과 알림의 기반이 된다.
4.2. 상태 변경과 알림(Notify) 과정
4.2. 상태 변경과 알림(Notify) 과정
Subject (주체)의 내부 상태가 변경되면, 이는 알림 과정의 시작점이 된다. 주체는 일반적으로 자신의 상태를 변경하는 메서드(예: setState()) 내부에서 Observer (관찰자)들에게 알림을 전송하는 메서드(예: notifyObservers())를 호출한다. 이 호출은 상태 변경의 직접적인 결과로 이루어지며, 주체는 어떤 관찰자가 어떤 구체적인 변화에 관심 있는지 알 필요가 없다.
알림 메서드가 실행되면, 주체는 자신에게 등록된 모든 관찰자 객체의 목록을 순회한다. 각 관찰자에 대해 미리 약속된 인터페이스 메서드(예: update())를 호출한다. 이 메서드를 호출할 때, 주체는 필요한 경우 변경된 상태 정보를 매개변수로 전달하거나, 관찰자가 주체 객체 자신을 참조하여 필요한 정보를 다시 가져오도록 할 수 있다. 이 과정에서 주체와 관찰자는 느슨하게 결합된 상태를 유지한다.
관찰자의 update() 메서드가 호출되면, 각 관찰자는 자신의 로직에 따라 주체의 상태 변화에 대응한다. 이 대응은 화면 갱신, 데이터 기록, 다른 객체에 대한 작업 트리거 등 다양할 수 있다. 모든 등록된 관찰자에 대한 알림이 완료되면 하나의 상태 변경 주기가 종료된다.
단계 | 주체의 행동 | 관찰자의 행동 |
|---|---|---|
1. 상태 변경 |
| - |
2. 알림 시작 |
| - |
3. 알림 전달 | 목록의 각 관찰자에 대해 |
|
4. 대응 실행 | - | 전달받은 정보를 바탕으로 구체적인 비즈니스 로직 실행. |
4.3. 해지(Unsubscribe) 과정
4.3. 해지(Unsubscribe) 과정
관찰자가 더 이상 주체(Subject)의 상태 변화를 받아보지 않으려면 등록을 해지해야 합니다. 이 과정은 등록(Subscribe) 과정의 역순으로 진행됩니다. 일반적으로 Observer 인터페이스를 구현한 객체 자신을 인자로 넘겨, Subject의 등록 목록에서 자신을 제거하는 방식으로 이루어집니다.
구체적인 해지 과정은 다음과 같습니다.
1. 관찰자(예: ConcreteObserverA)는 주체 객체의 해지 메서드(예: removeObserver(), detach())를 호출합니다.
2. 호출 시, 관찰자는 자기 자신(예: this)을 메서드의 인자로 전달합니다.
3. 주체는 내부적으로 유지하는 관찰자 목록(예: List<Observer>)에서 해당 인자와 일치하는 관찰자 객체를 찾아 제거합니다.
해지가 정상적으로 완료되면, 이후 발생하는 주체의 상태 변경과 알림(Notify) 과정에서는 해당 관찰자에게 더 이상 업데이트 메시지가 전송되지 않습니다. 이 메커니즘은 메모리 누수를 방지하는 데 중요합니다. 사용이 끝난 관찰자 객체를 주체가 계속 참조하지 않도록 명시적으로 해지하는 것은 가비지 컬렉션이 정상적으로 동작하도록 보장합니다.
단계 | 주체(Subject)의 역할 | 관찰자(Observer)의 역할 |
|---|---|---|
1. 해지 요청 | 해지 메서드를 제공하고 호출을 받음 | 주체의 해지 메서드를 호출하며 자신을 인자로 전달 |
2. 목록에서 제거 | 내부 관찰자 목록에서 전달받은 관찰자 객체를 검색 및 제거 | - |
3. 결과 | 이후 알림 시 제거된 관찰자는 제외됨 | 더 이상 상태 변경 알림을 받지 않음 |
일부 구현에서는 관찰자가 생성되거나 등록될 때 반환되는 구독 토큰(예: Subscription 객체)을 이용해 해지하기도 합니다. 이 방식은 관찰자가 자신의 참조를 직접 노출하지 않고도 해지를 관리할 수 있게 합니다.
5. 구현 예시
5. 구현 예시
구현 예시는 옵저버 패턴의 개념을 구체적인 코드로 보여준다. 다양한 프로그래밍 언어로 패턴을 어떻게 적용하는지 이해하는 데 도움이 된다.
Java 예제 코드
Java에서는 주로 java.util.Observable 클래스와 java.util.Observer 인터페이스를 사용했으나, 현재는 더 유연한 구현을 위해 직접 인터페이스를 정의하는 것이 일반적이다. 다음은 직접 정의한 인터페이스를 사용한 예시이다.
```java
// 주체(Subject) 인터페이스
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 관찰자(Observer) 인터페이스
interface Observer {
void update(String message);
}
// 구체적인 주체 클래스
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state);
}
}
}
// 구체적인 관찰자 클래스
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received update: " + message);
}
}
```
Python 예제 코드
Python은 동적 타입 언어의 특징을 살려 더 간결하게 패턴을 구현할 수 있다. 아래 예제는 추상 베이스 클래스(ABC)를 사용한 방식이다.
```python
from abc import ABC, abstractmethod
# 관찰자 인터페이스
class Observer(ABC):
@abstractmethod
def update(self, message):
pass
# 주체 클래스
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer: Observer):
self._observers.append(observer)
def detach(self, observer: Observer):
self._observers.remove(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
# 구체적인 관찰자
class ConcreteObserver(Observer):
def __init__(self, name):
self.name = name
def update(self, message):
print(f"{self.name} received: {message}")
# 사용 예
if __name__ == "__main__":
subject = Subject()
observer1 = ConcreteObserver("Observer1")
observer2 = ConcreteObserver("Observer2")
subject.attach(observer1)
subject.attach(observer2)
subject.notify("State Changed!") # 두 관찰자 모두 알림을 받는다.
subject.detach(observer1)
subject.notify("Another Change!") # observer2만 알림을 받는다.
```
두 언어의 예시는 모두 느슨한 결합을 달성하기 위해 인터페이스 또는 추상 클래스를 통해 주체와 관찰자를 분리하는 공통된 구조를 보여준다. 주체는 관찰자의 구체적인 클래스를 알 필요 없이, 오직 Observer 인터페이스에 정의된 update 메서드만 호출한다. 이는 새로운 관찰자 유형을 쉽게 추가할 수 있게 해주는 패턴의 핵심 장점이다.
5.1. Java 예제 코드
5.1. Java 예제 코드
옵저버 패턴을 자바로 구현할 때는 일반적으로 java.util 패키지에 포함된 Observer 인터페이스와 Observable 클래스를 사용할 수 있다. 그러나 이들은 JDK 9부터 사용이 권장되지 않는다. 따라서 현대적인 자바 코드에서는 주체(Subject)와 관찰자(Observer) 인터페이스를 직접 정의하여 구현하는 방식이 더 선호된다.
다음은 직접 인터페이스를 정의한 간단한 구현 예시이다. Subject 인터페이스는 옵저버의 등록, 제거, 알림 기능을 선언하고, Observer 인터페이스는 상태 업데이트 메서드를 선언한다.
```java
// 주체(Subject) 인터페이스
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 관찰자(Observer) 인터페이스
interface Observer {
void update(String message);
}
// 구체적인 주체 클래스
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String newState) {
this.state = newState;
notifyObservers(); // 상태 변경 시 모든 관찰자에게 알림
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(state); // 각 관찰자의 update 메서드 호출
}
}
}
// 구체적인 관찰자 클래스
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "이(가) 메시지를 수신함: " + message);
}
}
// 사용 예시
public class ObserverPatternDemo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("옵저버1");
Observer observer2 = new ConcreteObserver("옵저버2");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setState("새로운 상태 1"); // 두 옵저버 모두 알림을 받음
subject.removeObserver(observer1);
subject.setState("새로운 상태 2"); // 옵저버2만 알림을 받음
}
}
```
이 예제에서 ConcreteSubject의 setState 메서드가 호출되면 내부 상태가 변경되고, notifyObservers 메서드를 통해 등록된 모든 Observer 객체의 update 메서드가 호출된다. 실행 결과는 다음과 같다.
```
옵저버1이(가) 메시지를 수신함: 새로운 상태 1
옵저버2이(가) 메시지를 수신함: 새로운 상태 1
옵저버2이(가) 메시지를 수신함: 새로운 상태 2
```
자바 9 이상의 환경에서는 java.beans 패키지의 PropertyChangeListener와 PropertyChangeSupport를 사용하는 방법도 일반적이다. 또한, 리액티브 프로그래밍 라이브러리인 RxJava나 Project Reactor의 Observable 또는 Flux는 이 패턴을 발전시킨 형태로 볼 수 있다.
5.2. Python 예제 코드
5.2. Python 예제 코드
옵저버 패턴의 Python 구현은 언어의 동적 특성과 데코레이터 등의 기능을 활용하여 비교적 간결하게 표현할 수 있다. 인터페이스를 명시적으로 정의하지 않아도 덕 타이핑을 통해 동작을 구현하는 것이 일반적이다. 아래 예제는 기본적인 주체(Subject)와 관찰자(Observer) 클래스를 정의하고, 상태 변경 시 알림을 전파하는 과정을 보여준다.
```python
class Subject:
"""관찰 대상이 되는 주체 클래스"""
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
"""관찰자를 등록한다."""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer):
"""관찰자 등록을 해지한다."""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self):
"""모든 등록된 관찰자에게 상태 변경을 알린다."""
for observer in self._observers:
observer.update(self)
@property
def state(self):
return self._state
@state.setter
def state(self, value):
self._state = value
self.notify() # 상태가 설정되면 자동으로 알림을 발생시킨다.
class ConcreteObserver:
"""구체적인 관찰자 클래스"""
def __init__(self, name):
self.name = name
self.observer_state = None
def update(self, subject):
"""주체로부터 알림을 받아 자신의 상태를 갱신한다."""
self.observer_state = subject.state
print(f"Observer {self.name}: 상태가 '{self.observer_state}'로 업데이트되었다.")
# 사용 예시
if __name__ == "__main__":
subject = Subject()
observer_a = ConcreteObserver("A")
observer_b = ConcreteObserver("B")
subject.attach(observer_a)
subject.attach(observer_b)
subject.state = "Ready" # 두 관찰자 모두에게 알림이 전송된다.
subject.detach(observer_b)
subject.state = "Running" # 관찰자 B는 해지되었으므로 A만 알림을 받는다.
```
보다 함수형 접근을 위해 콜백 함수를 관찰자로 직접 등록하는 방식도 자주 사용된다. 이 방식은 클래스 정의 없이 간단한 함수로 관찰자 로직을 구현할 수 있다는 장점이 있다. 또한, 파이썬의 @property 데코레이터를 활용하면 상태 변경 시점을 명확히 캡슐화하여 notify 메서드를 자동으로 호출할 수 있다.
6. 장점과 단점
6. 장점과 단점
옵저버 패턴은 객체 간의 일대다 종속성을 정의하여, 한 객체의 상태가 변할 때 그 객체에 의존하는 모든 객체들이 자동으로 알림을 받고 갱신될 수 있도록 한다. 이 패턴은 소프트웨어 설계에서 널리 사용되며, 명확한 장점과 함께 몇 가지 고려해야 할 단점을 가지고 있다.
장점
이 패턴의 주요 장점은 결합도를 낮추는 것이다. Subject와 Observer는 느슨하게 결합되어 있어, 서로에 대한 구체적인 정보 없이도 상호작용할 수 있다. 이는 Subject의 구현을 변경하지 않고도 새로운 유형의 Observer를 쉽게 추가할 수 있게 하여 시스템의 확장성을 크게 향상시킨다. 또한, 상태 변경을 통보하는 책임이 Subject에게 집중되므로, 여러 Observer 객체들에게 직접 상태를 전파하는 복잡한 코드를 제거할 수 있다. 런타임 중에 객체 간의 관계를 동적으로 설정하거나 해제할 수 있는 유연성도 중요한 장점이다.
단점
그러나 이 패턴에는 주의해야 할 단점도 존재한다. 가장 큰 문제는 알림 순서에 대한 보장이 없다는 점이다. Observer들이 알림을 받는 순서는 구현에 따라 달라질 수 있어, 특정 순서에 의존하는 로직에서는 문제를 일으킬 수 있다. 또한, 간접적인 통신 구조로 인해 디버깅이 어려워질 수 있다. 상태 변경의 원인과 그에 따른 여러 Observer의 반응을 추적하기가 복잡해질 수 있다. 마지막으로, Observer를 등록한 후 적절히 해지하지 않으면 Observer 객체가 메모리에 계속 참조되어 메모리 누수가 발생할 수 있다. 특히 많은 수의 Observer를 관리할 때 성능 저하가 발생할 수도 있다.
6.1. 장점
6.1. 장점
옵저버 패턴의 주요 장점은 결합도를 낮추고 시스템의 유연성을 높이는 데 있다. 주체(Subject)와 관찰자(Observer)가 느슨하게 연결되어 있어, 서로의 내부 구현에 대해 알 필요가 없다. 이는 한 객체의 상태 변화가 여러 객체에 자동으로 통지되어야 하는 상황, 특히 일대다(one-to-many) 의존 관계를 정의할 때 매우 효과적이다.
이 패턴은 새로운 관찰자 클래스를 추가하거나 기존 관찰자를 제거하는 작업을 런타임 중에 쉽게 수행할 수 있게 한다. 주체 객체의 코드를 수정하지 않고도 새로운 유형의 관찰자를 시스템에 통합할 수 있어 개방-폐쇄 원칙을 잘 준수한다. 예를 들어, 로그를 출력하는 관찰자나 데이터베이스에 저장하는 관찰자를 기존 시스템에 추가하는 것이 간단해진다.
장점 | 설명 |
|---|---|
결합도 감소 | 주체와 관찰자가 추상 인터페이스에만 의존하여 강한 결합을 피한다. |
유연한 확장성 | 새로운 관찰자 추가 및 제거가 런타임에 가능하며 주체 코드 변경이 필요 없다. |
상태 동기화 자동화 | 주체의 상태 변경이 등록된 모든 관찰자에게 자동으로 전파된다. |
재사용성 향상 | 주체와 관찰자 클래스를 다른 맥락에서 독립적으로 재사용하기 쉽다. |
이러한 특성 덕분에 이벤트 드리븐 프로그래밍이나 실시간 데이터 업데이트가 필요한 사용자 인터페이스, 모니터링 시스템 등 다양한 도메인에서 널리 적용된다.
6.2. 단점
6.2. 단점
옵저버 패턴은 상태 변화를 효과적으로 전파하지만, 몇 가지 주의해야 할 단점을 가지고 있다.
첫째, 알림 순서가 보장되지 않는다. 주체(Subject)가 여러 관찰자(Observer)에게 상태 변경을 알릴 때, 등록된 순서나 특정 순서대로 알림이 전달된다는 보장이 없다. 이는 관찰자(Observer)들 간의 실행 순서에 의존적인 로직이 있을 경우 예기치 않은 동작을 초래할 수 있다. 둘째, 성능 저하의 가능성이 있다. 많은 수의 관찰자가 등록되어 있거나, 상태 변경이 빈번하게 발생하는 경우, 모든 관찰자에게 일일이 알림을 전달하는 과정이 시스템 부하를 증가시킨다. 특히 관찰자 중 하나의 처리 속도가 느리면 전체 알림 흐름이 지연될 수 있다.
셋째, 메모리 누수(Memory Leak) 위험이 존재한다. 관찰자가 더 이상 필요하지 않음에도 해지하지 않고 등록된 상태로 남아 있으면, 가비지 컬렉션(Garbage Collection)의 대상이 되지 않아 메모리가 회수되지 않을 수 있다. 이는 장시간 실행되는 애플리케이션에서 심각한 문제가 될 수 있다. 마지막으로, 디버깅이 복잡해질 수 있다. 상태 변화의 원인과 결과가 여러 객체에 분산되어 추적하기 어려우며, 알림이 연쇄적으로 발생하는 경우(예: 한 관찰자의 동작이 다시 주체의 상태를 변경함) 실행 흐름을 이해하는 데 어려움을 겪을 수 있다.
7. 적용 사례
7. 적용 사례
옵저버 패턴은 객체 간의 일대다 종속성을 정의하여 한 객체의 상태가 변하면 그 객체에 의존하는 모든 객체들이 자동으로 알림을 받고 갱신될 수 있도록 하는 디자인 패턴이다. 이 패턴은 다양한 소프트웨어 도메인에서 널리 활용된다.
가장 대표적인 적용 사례는 GUI 프로그래밍이다. 버튼 클릭, 마우스 이동, 키 입력과 같은 사용자 이벤트를 처리할 때 이벤트 리스너나 핸들러가 옵저버 패턴의 구체적인 구현 형태로 사용된다. 예를 들어, 버튼 객체(Subject)에 여러 개의 액션 리스너(Observer)를 등록해 두면, 사용자가 버튼을 클릭할 때마다 등록된 모든 리스너의 actionPerformed 같은 메서드가 호출된다. 이는 MVC 아키텍처에서도 핵심적으로 적용되며, 모델(Subject)의 데이터가 변경되면 이를 관찰하는 뷰(Observer)들이 자동으로 갱신되어 사용자 인터페이스의 일관성을 유지한다.
또 다른 주요 적용 분야는 발행-구독 모델이다. 이 모델은 옵저버 패턴의 확장된 형태로, 발행자(Publisher)와 구독자(Subscriber) 사이에 메시지 브로커나 이벤트 버스가 존재하는 경우가 많다. 구독자는 특정 주제나 채널에 관심을 등록하고, 발행자가 해당 주제로 메시지를 발행하면 모든 구독자에게 비동기적으로 알림이 전달된다. 이는 뉴스 피드, 실시간 데이터 스트리밍, 마이크로서비스 간의 이벤트 통신 등 현대 분산 시스템에서 광범위하게 사용된다.
적용 영역 | 주체(Subject) 역할 | 관찰자(Observer) 역할 | 주요 특징 |
|---|---|---|---|
GUI / 이벤트 처리 | 버튼, 슬라이더 등 UI 컴포넌트 | 이벤트 리스너, 핸들러 | 사용자 상호작용에 반응 |
MVC 아키텍처 | 모델 (데이터) | 뷰 (표현 계층) | 데이터와 UI의 분리 및 동기화 |
발행-구독 모델 | 메시지 발행자, 이벤트 발생기 | 메시지 구독자, 이벤트 핸들러 | 느슨한 결합, 비동기 통신 지원 |
이 외에도 소프트웨어의 설정 변경 감지, 주식 시세 모니터링 시스템, 로깅 프레임워크에서 여러 출력 대상(콘솔, 파일, 네트워크)으로 로그를 전파하는 경우 등에서도 옵저버 패턴이 활용된다. 이 패턴은 상태 변화를 여러 객체에 효율적으로 전파해야 하는 모든 상황에 적합한 해결책을 제공한다.
7.1. GUI 프로그래밍 (이벤트 리스너)
7.1. GUI 프로그래밍 (이벤트 리스너)
옵저버 패턴은 사용자 인터페이스 프로그래밍, 특히 이벤트 구동 프로그래밍에서 가장 널리 적용되는 패턴 중 하나이다. GUI 컴포넌트(예: 버튼, 텍스트 필드, 슬라이더)는 상태 변경(클릭, 텍스트 입력, 값 변경)이 발생하는 주체가 되고, 이러한 변경에 반응해야 하는 코드(예: 이벤트 핸들러)는 관찰자가 된다. 이 구조는 컴포넌트와 비즈니스 로직 간의 강한 결합을 제거하여 유연하고 재사용 가능한 코드를 작성할 수 있게 한다.
대부분의 현대 GUI 프레임워크는 이 패턴을 기반으로 한 이벤트 리스너 시스템을 내장하고 있다. 예를 들어, Java의 Swing이나 JavaFX, .NET의 WinForms나 WPF, 웹 프론트엔드의 JavaScript DOM 이벤트 모델이 이에 해당한다. 사용자는 버튼에 addActionListener나 addEventListener와 같은 메서드를 통해 자신의 콜백 함수를 등록하기만 하면, 나머지 알림 메커니즘은 프레임워크가 담당한다.
구성 요소 | GUI 프로그래밍에서의 역할 | 예시 |
|---|---|---|
Subject (주체) | GUI 컴포넌트 (위젯) |
|
Observer (관찰자) | 이벤트 리스너 (핸들러) |
|
등록 메서드 | 리스너 추가 메서드 |
|
상태 변경 | 사용자 상호작용 | 버튼 클릭, 키 입력, 마우스 이동 |
알림 메서드 | 프레임워크 내부의 이벤트 발송 |
|
이 패턴의 적용 덕분에, 하나의 버튼에 여러 개의 독립적인 액션을 등록하거나, 런타임에 동적으로 리스너를 추가 및 제거하는 것이 가능해진다. 또한, GUI 컴포넌트는 자신을 누가 관찰하는지 알 필요 없이 오직 상태 변화만을 알리면 되므로, 단일 책임 원칙을 잘 준수한다. 결과적으로, UI와 애플리케이션 로직이 명확하게 분리되어 테스트와 유지보수가 용이해진다.
7.2. 발행-구독(Pub-Sub) 모델
7.2. 발행-구독(Pub-Sub) 모델
발행-구독 모델은 옵저버 패턴의 확장된 형태로, 발행자와 구독자 사이에 메시지 브로커 또는 이벤트 채널이 존재하는 것이 특징이다. 발행자는 특정 채널이나 토픽에 메시지를 발행하기만 하며, 실제로 어떤 구독자가 있는지 알 필요가 없다. 마찬가지로 구독자는 관심 있는 토픽을 구독하고 해당 토픽의 메시지를 수신하지만, 발행자의 정체를 알지 못한다. 이 중간 매개체는 메시지의 라우팅, 필터링, 때로는 지속성을 담당하여 발행자와 구독자 간의 결합도를 더욱 낮춘다.
옵저버 패턴이 일반적으로 단일 애플리케이션 내에서 객체 간의 직접적인 통신에 사용된다면, 발행-구독 모델은 분산 시스템에서 널리 적용된다. 예를 들어, 마이크로서비스 아키텍처에서 서비스 간의 비동기 통신, 실시간 데이터 스트리밍, 또는 로그 집계 시스템에서 이 모델을 활용한다. 대표적인 구현체로는 Apache Kafka, RabbitMQ, Redis Pub/Sub 등이 있다.
다음 표는 전통적인 옵저버 패턴과 발행-구독 모델의 주요 차이점을 보여준다.
특징 | 옵저버 패턴 | 발행-구독 모델 |
|---|---|---|
결합도 | 발행자와 구독자가 서로를 모르므로 매우 낮음 | |
통신 방식 | 대부분 동기적 호출 | 대부분 비동기적 메시징 |
중간 매개체 | 존재하지 않음 | 메시지 브로커 또는 이벤트 버스가 필수적 |
적용 범위 | 단일 프로세스 또는 애플리케이션 내부 | 분산 시스템, 프로세스 간, 네트워크를 넘어선 통신 |
메시지 필터링 | Subject가 모든 Observer에게 동일한 알림 전달 | 구독자가 특정 토픽이나 조건에 따라 메시지 선택 수신 가능 |
이러한 구조 덕분에 발행-구독 모델은 시스템의 확장성과 유연성을 크게 향상시킨다. 새로운 구독자를 추가하거나 제거해도 발행자 코드를 변경할 필요가 없으며, 발행자의 부하 증가 없이 다수의 구독자를 처리할 수 있다.
7.3. MVC 아키텍처
7.3. MVC 아키텍처
MVC 아키텍처에서 옵저버 패턴은 모델(Model), 뷰(View), 컨트롤러(Controller) 간의 느슨한 결합을 유지하는 핵심 메커니즘으로 작동한다. 모델은 애플리케이션의 데이터와 비즈니스 로직을 담당하는 주체(Subject)가 되며, 하나 이상의 뷰는 해당 데이터의 표현을 담당하는 관찰자(Observer) 역할을 한다. 모델의 상태가 변경되면 등록된 모든 뷰에게 알림을 전송하여, 뷰가 자동으로 최신 상태를 반영한 화면을 갱신하도록 한다.
이 패턴의 적용은 사용자 인터페이스와 데이터의 일관성을 보장하면서도 구성 요소들을 분리한다. 컨트롤러는 사용자 입력을 처리하고 모델을 업데이트하는 역할을 하지만, 모델이 직접 어떤 뷰를 갱신할지 알 필요는 없다. 대신 모델은 단순히 '상태가 변경되었다'는 사실만을 알리고, 각 뷰는 자신의 방식으로 이 변경에 반응한다. 이를 통해 동일한 모델에 대해 여러 개의 다른 뷰(예: 차트 뷰, 테이블 뷰)를 독립적으로 생성하고 연결하는 것이 가능해진다.
구체적인 데이터 흐름은 다음 표와 같다.
단계 | 구성 요소 | 역할 |
|---|---|---|
1. 등록 | 뷰 | 모델의 변경 알림을 받기 위해 자신을 관찰자로 등록한다. |
2. 변경 | 컨트롤러 | 사용자 액션에 반응하여 모델의 상태를 수정한다. |
3. 알림 | 모델 | 내부 상태 변경 시 등록된 모든 뷰에게 |
4. 갱신 | 뷰 | 알림을 받고 모델로부터 최신 데이터를 가져와 자신을 다시 그린다. |
이러한 구조는 GUI 애플리케이션뿐만 아니라 웹 애플리케이션의 서버사이드 아키텍처에서도 흔히 발견된다. 예를 들어, 백엔드의 도메인 모델이 변경될 때 관련된 여러 프론트엔드 컴포넌트나 다른 서비스에 자동으로 동기화 신호를 보내는 경우에도 동일한 패턴이 적용된다. MVC에서 옵저버 패턴은 변화를 효율적으로 전파하여 애플리케이션의 유지보수성과 확장성을 높이는 데 기여한다.
8. 다른 패턴과의 관계
8. 다른 패턴과의 관계
옵저버 패턴은 객체 간의 일대다 종속성을 정의하는 행동 디자인 패턴이다. 이 패턴은 미디에이터 패턴 및 전략 패턴과 구조적, 개념적 유사점과 차이점을 공유한다.
미디에이터 패턴과의 주요 차이는 중앙 조정자의 존재 유무이다. 미디에이터 패턴은 여러 객체 간의 직접적인 통신을 금지하고, 모든 상호작용이 중앙 미디에이터 객체를 통해 이루어지도록 강제한다. 이는 객체 간의 결합도를 낮추지만, 미디에이터 자체가 복잡해질 수 있다는 단점이 있다. 반면 옵저버 패턴은 Subject와 Observer가 직접적인 링크를 통해 연결되지만, 느슨한 결합을 유지한다. Subject는 Observer의 구체적인 클래스를 알 필요 없이 인터페이스만 알면 된다. 두 패턴 모두 객체 간의 결합도를 줄이는 목적을 가지지만, 미디에이터는 '다대다' 관계를 정리하는 데, 옵저버는 '일대다' 관계의 상태 전파에 초점을 맞춘다.
전략 패턴과는 객체 구성과 행위 캡슐화 측면에서 연관성을 가진다. 전략 패턴은 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만든다. 이는 Context 객체의 행위를 런타임에 변경할 수 있게 한다. 옵저버 패턴에서 Subject가 Observer 객체 목록을 구성(Composition)으로 유지하는 방식은, 전략 패턴에서 Context가 Strategy 객체를 구성으로持有하는 방식과 유사하다. 둘 다 실행 시점에 객체의 행동을 변경하거나 확장할 수 있는 유연성을 제공한다. 그러나 전략 패턴이 단일 객체의 알고리즘을 변경하는 '일대일' 관계라면, 옵저버 패턴은 하나의 상태 변화를 여러 객체에 알리는 '일대다' 관계라는 근본적인 목적의 차이가 존재한다.
비교 요소 | 옵저버 패턴 | 미디에이터 패턴 | 전략 패턴 |
|---|---|---|---|
관계 형태 | 일대다 (One-to-Many) | 다대다 (Many-to-Many) | 일대일 (One-to-One) |
주요 목적 | 상태 변경 알림 | 객체 간 통신 중앙화 | 알고리즘 교체 |
결합도 | Subject와 Observer 간 느슨한 결합 | Colleague 객체들 간 결합도 없음 | Context와 Strategy 간 느슨한 결합 |
통신 흐름 | Subject → Observer (단방향) | Colleague ↔ Mediator (양방향) | Context → Strategy (위임) |
8.1. 미디에이터 패턴과의 비교
8.1. 미디에이터 패턴과의 비교
옵저버 패턴과 미디에이터 패턴은 모두 객체 간의 복잡한 상호작용을 관리하는 행동 디자인 패턴이지만, 그 접근 방식과 목적에 있어 명확한 차이점이 존재합니다.
두 패턴의 핵심 차이는 객체 간의 결합 방식에 있습니다. 옵저버 패턴은 주체(Subject)와 관찰자(Observer) 사이에 일대다(one-to-many) 의존성을 정의합니다. 주체의 상태가 변경되면 등록된 모든 관찰자 객체에게 자동으로 알림이 브로드캐스트됩니다. 이는 관찰자들이 주체에 직접 연결되어 있음을 의미합니다. 반면, 미디에이터 패턴은 객체들 사이에 미디에이터(Mediator)라는 중재자를 도입하여 객체 간의 직접적인 통신을 제거합니다. 객체들은 서로를 알지 못하며, 모든 통신은 미디에이터를 통해 중개됩니다. 이는 다대다(many-to-many) 관계를 단순화하여 객체 간의 결합도를 현저히 낮춥니다.
비교 항목 | 옵저버 패턴 | 미디에이터 패턴 |
|---|---|---|
주요 목적 | 한 객체의 상태 변화를 여러 객체에 알림 | 객체 간의 복잡한 상호작용을 중앙에서 조정 |
통신 구조 | 일대다(One-to-Many) | 중개를 통한 다대다(Many-to-Many) |
결합도 | 주체와 관찰자 간 느슨한 결합 | 객체들 간 결합도가 매우 낮음 (미디에이터와만 연결) |
제어 흐름 | 분산형 알림 (주체가 여러 관찰자에게 직접 통지) | 중앙 집중형 조정 (모든 요청이 미디에이터를 통함) |
적용 영역도 구분됩니다. 옵저버 패턴은 이벤트 드리븐 시스템, 실시간 데이터 피드, MVC 아키텍처에서 모델과 뷰를 분리하는 데 널리 사용됩니다. 미디에이터 패턴은 사용자 인터페이스에서 여러 위젯이 복잡하게 상호작용하거나, 컴포넌트 기반 시스템에서 컴포넌트들의 협력을 조정할 때 유용합니다. 요약하면, 옵저버 패턴이 '상태 변화의 전파'에 초점을 맞춘다면, 미디에이터 패턴은 '상호작용 자체의 캡슐화와 단순화'에 초점을 맞춥니다.
8.2. 전략 패턴과의 연관성
8.2. 전략 패턴과의 연관성
옵저버 패턴과 전략 패턴은 모두 객체 간의 관계를 유연하게 정의하는 행동 디자인 패턴이지만, 그 목적과 적용 방식에서 차이를 보인다. 두 패턴은 종종 함께 사용되거나, 하나의 패턴 내에서 다른 패턴의 요소를 활용하는 방식으로 연관성을 가진다.
핵심적인 연관성은 전략 패턴이 옵저버 패턴의 알림(Notify) 메커니즘을 구현하는 데 사용될 수 있다는 점이다. 옵저버 패턴에서 Subject (주체)는 상태 변경 시 등록된 모든 Observer (관찰자)에게 알림을 보낸다. 이때 각 Observer (관찰자)에게 어떤 방식으로 알릴지(예: 동기/비동기, 즉시/지연)의 전략을 전략 패턴을 통해 캡슐화하여 주입할 수 있다. 이는 Subject (주체)의 알림 로직을 변경하지 않고도 알림 방식을 유연하게 교체할 수 있게 한다.
다음 표는 두 패턴의 주요 초점과 관계를 비교한다.
비교 항목 | 옵저버 패턴 | 전략 패턴 |
|---|---|---|
주요 목적 | 객체 간의 일대다 종속성 정의, 상태 변경 알림 | 알고리즘 군을 정의하고 각각을 캡슐화하여 상호 교체 가능하게 함 |
관계의 성격 | Subject (주체)와 Observer (관찰자) 간의 구독 관계 | Context (문맥)와 Strategy (전략) 간의 위임 관계 |
변화의 주체 | Subject (주체)의 상태 변화 | Context (문맥)에서 사용하는 알고리즘(전략)의 변화 |
연관성 | 알림 전달 방식 등 패턴 내부 동작을 전략화할 수 있음 | 상태 변화를 관찰하는 옵저버를 전략으로 구현할 수 있음 |
또한, Observer (관찰자) 객체 자체가 특정 반응 알고리즘을 구현한 전략 패턴의 ConcreteStrategy (구체적 전략)으로 간주될 수 있다. Subject (주체)의 상태 변화에 대해 각 Observer (관찰자)가 수행하는 구체적인 업데이트 동작은 서로 다른 전략으로 볼 수 있다. 따라서 두 패턴은 상호 보완적으로 설계에 적용되어, 변화에 대한 반응이라는 큰 흐름에서 더 높은 수준의 유연성을 제공한다.
